<?php
declare(strict_types=1);

namespace lib\data;

use lib\data\virtual\GoodsOrder;
use Serializable;
use sys\Log;

class StoreComponents extends \lib\sync\SyncableData {
    use traits\MidStoreTrait;
    use traits\WaitLock;

    protected static string $SyncKey = 'StoreComponents';
    protected static string $TableName = 'el_components_store_map';


    const CALC_MODE_PACK    = 0;        # 按件计价
    const CALC_MODE_PIECE   = 1;        # 按个计价


    const DOC_TYPE_PURCHASE         = 0;        # 采购
    const DOC_TYPE_SHIPMENT         = 1;        # 出货(非销售)
    const DOC_TYPE_ONRACK           = 2;        # 移库(上架)
    const DOC_TYPE_OFFRACK          = 3;        # 移库(下架)
    const DOC_TYPE_CHECK            = 4;        # 盘点  (盘盈盘亏)
    const DOC_TYPE_RETURN           = 5;        # 退货
    const DOC_TYPE_GOODS_ARRANGE    = 6;        # 商品整理
    const DOC_TYPE_RACK_ARRANGE     = 7;        # 货架整理
    const DOC_TYPE_SALE_BAR         = 8;        # 销售(吧台)
    const DOC_TYPE_SALE_STA         = 9;        # 销售(客户端)
    const DOC_TYPE_SALE_NET         = 10;       # 销售(网络)


    protected int $id;
    protected int $mid;
    protected int $store_id;        # 货架ID
    protected int $goods_id;        # 商家商品ID
    protected int $store_num;
    protected bool $on_sale;

    protected static array $propNamesC = ['id','mid', 'store_num', 'store_id', 'components_id','on_sale'];
    protected static array $propNamesU = ['id','mid', 'store_num', 'store_id', 'components_id','on_sale'];
    protected static array $propNamesJ = ['id','mid', 'store_num', 'store_id', 'components_id','on_sale'];

    
    public function __construct(array &$data)
    {
        parent::__construct($data);
        $this->traitInit();
    }

    public static function setOnSale(int $mid, int $store_id, int $goods_id, bool $onSale) :array {

        if(null === ($store = \lib\data\Store::get($store_id)) || $store->mid != $mid){
            return ['success'=>false, 'message'=>'仓库不存在.'];
        }

        $isNewGoods = false;
        $db = new \sys\Db();
        $syncObject = new \lib\sync\Synchronize($mid, $db);
        $StoreComponents = \lib\data\StoreComponents::waitGoodsFromStoreId($db, $store_id, $goods_id, $isNewGoods);
        if(null == $StoreComponents){
            return ['success'=>false, 'message'=>'元件不存在.'];
        }
        if($isNewGoods){
            $syncObject->put($StoreComponents->getSyncName(), $StoreComponents);
        }

        $goods = \lib\data\Components::get($goods_id)->waitLock();

       
        
        if($onSale && !$goods->on_sale){
            return ['success'=>false, 'message'=>'元件已被禁售, 不可修改!', 'data'=>['on_sale'=>false]];
        }

        if(null !== ($changed = ($StoreComponents->where(['on_sale'=>!$onSale])->update(['on_sale'=>$onSale])))){
            $db->table('tp_nb_goods_store_map')->where([['id','=',$goods->id]])->update($changed);
            
            $syncObject->up($StoreComponents->getSyncName(), $StoreComponents->id, $changed);

            if(null !== ($changed = $goods->update(['num'=>$goods->num + ($onSale ? $StoreComponents->store_num : - $StoreComponents->store_num)]))){
                $db->table('tp_nb_goods_rack')->where([['id','=',$goods->id]])->update($changed);
                $syncObject->up($goods->getSyncName(), $goods->id, $changed);
            }

            return ['success'=>true, 'message'=>'设置成功.', 'data'=>['on_sale'=>$StoreComponents->on_sale]];
        }

        return ['success'=>false, 'message'=>'未做任何修改.', 'data'=>['on_sale'=>$StoreComponents->on_sale]];
    }

    /**
     * 采购|出货
     */
    public static function purchase(int $mid, int $uid, int $storeId, int $calcMode, int $settleMode, ?float $realpay, bool $in, float $sum, array $goodsInfo) {

        $store = \lib\data\Store::get($storeId);
        if(null === $store || $store->mid != $mid){
            return ['success'=>false, 'message'=>'仓库不存在.'];
        }

        $db = new \sys\Db();
        $no = \lib\Utils::genOrderNo('P');
        $syncObject = new \lib\sync\Synchronize($mid, $db);
        $time = time();
        $sumPrice = 0;
        $isNew = false;     # 是否新建

        $merchant = \lib\Merchant::get($mid)->waitLock();
        foreach($goodsInfo as $i=>$g) { // id,  piece_price, num
            $lockids[] = $g['id'];
            if(null === ($StoreComponents = static::waitGoodsFromStoreId($db, $storeId, $g['id'], $isNew)) || $StoreComponents->mid !== $mid){
                return ['success'=>false, 'message'=>'商品不存在.'];
            }
            $goodsInfo[$i]['obj'] = $StoreComponents;
            if($isNew){
                $syncObject->put($StoreComponents->getSyncName(), $StoreComponents);
            }

            if(static::CALC_MODE_PACK == $calcMode) {
                $changedNum = $g['pack_size'] * $g['num'];
            }else{
                $changedNum = $g['num'];
            }

            if(!$in && $StoreComponents->store_num < $changedNum){
                return ['success'=>false, 'message'=>'库存不足.'];
            }
        }

        $tasks = [];
        $objectCached =  [];
        if(is_null($realpay)) $realpay = 0;
        $tasks[] = $tp_nb_goods_documents = $db->task('tp_nb_goods_documents')->subInsert([
            'mid'           => $mid,
            'document_no'   => $no,
            'document_type' => $in ? static::DOC_TYPE_PURCHASE : static::DOC_TYPE_SHIPMENT,
            'shift_id'      => $merchant->shift_id,
            'operator_uid'  => $uid,
            'settle_mode'   => $settleMode,
            'amount'        => $sumPrice,
            'realpay'       => $realpay,
            'desc'          => '',
            'createtime'    => time(),
        ]);
        $doc_id = new \sys\db\ExpValue($tp_nb_goods_documents->pkVarName());

        foreach($goodsInfo as $g) {
            $StoreComponents = $g['obj'];
            $lockids[] = $goods_id = $StoreComponents->goods_id;
            $goods = Components::get($goods_id);
            
            if(static::CALC_MODE_PACK == $calcMode) {
                $packSize   = (int)$g['pack_size'];              # 包装规格
                $packNum    = (int)$g['num'];                    # 包装数量
                $num        = $packNum * $packSize;              # 总数量
                $amount     = $packNum * $g['pack_price'];       # 总金额

                # 更新商品信息
                $oldValue   = [];
                if(null !== $changed = $goods->update(['pack_price'=>(float)$g['pack_price'], 'pack_size'=>$packSize], false, $oldValue)){
                    $tasks[]       = $db->table('tp_nb_goods_rack')->where([['id','=',$goods_id]])->subUpdate($changed);
                    $objectCached[]     = [$goods, $changed, $oldValue];
                }
            }else{
                $packSize   = 1;
                $packNum    = 1;
                $num        = $g['num'];
                $amount     = $num * $g['piece_price'];

                # 更新商品信息
                $oldValue   = [];
                if(null !== $changed = $goods->update(['piece_price'=>$g['piece_price']], false, $oldValue)){
                    $tasks[]       = $db->table('tp_nb_goods_rack')->where([['id','=',$goods_id]])->subUpdate($changed);
                    $objectCached[]     = [$goods, $changed, $oldValue];
                }
            }
            $sumPrice += $amount;
            $tasks[] = $db->task('el_components_changes')->subInsert([
                'mid'               => $mid,
                'shift_id'          => $merchant->shift_id,
                'document_id'       => $doc_id,
                'document_type'     => $in ? 1 : 2,
                'operator_uid'      => $uid,
                'components_id'     => $goods_id,
                'omponents_name'    => $goods->name,
                'store_id'          => $storeId,
                'store_name'        => $store->name,
                'store_before'      => $StoreComponents->store_num,
                'store_after'       => $in ? $StoreComponents->store_num + $num : $StoreComponents->store_num - $num,
                'num'               => $num,
                'target_id'         => 0,
                'target_name'       => '',
                'target_before'     => 0,
                'target_after'      => 0,
                'calc_mode'         => $calcMode,
                'pack_size'         => $packSize,
                'pack_num'          => $packNum,
                'amount'            => $amount,
                'createtime'        => $time,
            ]);

            # 更新货架库存
            $oldValue = [];
            if(null !== ($changed = $StoreComponents->update([
                'store_num' => $in ? $StoreComponents->store_num + $num : $StoreComponents->store_num - $num
            ], false, $oldValue))) {
                $tasks[] = $db->table('tp_nb_goods_store_map')->where([['id','=',$StoreComponents->id]])->subUpdate($changed);
                $objectCached[] = [$StoreComponents, $changed, $oldValue];
            }

            # 更新总库存
            if($store->prop == Store::IS_RACK && $StoreComponents->on_sale){
                $oldValue = [];
                if(null !== ($changed = $goods->update([
                    'num' => $in ? $goods->num + $num : $goods->num - $num
                ], false, $oldValue))) {
                    $tasks[] = $db->table('tp_nb_goods_rack')->where([['id','=',$goods->id]])->subUpdate($changed);
                    $objectCached[] = [$goods, $changed, $oldValue];
                }
            }
        }

        # 有支付金额
        Log::console("settleMode = {$settleMode}, realpay = {$realpay}", "DEBUG}");
        if($settleMode == GoodsOrder::SETTLE_MODE_CASH){
            if(is_null($realpay)){
                return ['success'=>false, 'message'=>'请输入实付金额.'];
            }
            if(abs($realpay) > 0.009){
                $beforeAmount = $merchant->amount;
                if($in){
                    if($merchant->amount < $realpay){
                        return ['success'=>false, 'message'=>'商户余额不足.'];
                    }
                    $oldValue = [];
                    if(null !== ($changed = $merchant->update(['amount'=>$merchant->amount - $realpay], false, $oldValue))){
                        $objectCached[] = [$merchant, $changed, $oldValue];
                    }
                    $tasks[] = $db->table('tp_nb_merchant_goods')->where([['id','=', $mid]])->dec('amount', $realpay)->subUpdate();
                }else{
                    $oldValue = [];
                    if(null !== ($changed = $merchant->update(['amount'=>$merchant->amount + $realpay], false, $oldValue))){
                        $objectCached[] = [$merchant, $changed, $oldValue];
                    }
                    $tasks[] = $db->table('tp_nb_merchant_goods')->where([['id','=', $mid]])->inc('amount', $realpay)->subUpdate();
                }
                
                $tasks[] = $db->task('tp_nb_goods_order_flow')->subInsert([
                    'mid'           => $mid,
                    'shift_id'      => $merchant->shift_id,
                    'order_id'      => 0,
                    'document_id'   => $doc_id,
                    'src'           => GoodsOrder::PAY_SRC_CASH,
                    'isrc'          => 0,
                    'amount'        => $in? - $realpay : $realpay,
                    'before'        => $beforeAmount,
                    'after'         => $merchant->amount,
                    'createtime'    => $time,
                ]);
                $shift = $merchant->getCurrentShift();

                # 更新班次统计
                $oldValue = [];
                if(null !== ($changed = (
                    $in ? $shift->onPurchase(GoodsOrder::PAY_MODE_CASH, $realpay, 0, $oldValue) : 
                            $shift->onSell(GoodsOrder::PAY_MODE_CASH, $realpay, 0, $oldValue)
                    ))){
                    $objectCached[] = [$shift, null, $oldValue];
                    $tasks[] = $db->task('tp_nb_goods_shift')->where($shift->id)->subUpdate($changed);
                }
            }
        }else{
            $realpay = 0;
        }
        
        $db->startTrans();
        try{
            if($db->batch_execute($tasks, \sys\Db::SQL_FIND)){
                $db->commit();

                # 发起同步
                $syncObject->makeSyncTask($tasks);
                Log::console('$tp_nb_goods_documents = ' . json_encode($tp_nb_goods_documents->getData()), 'DEBUG');

                # 发送同步
                foreach($objectCached as list($obj, $changed, $oldValue)){
                    if(null !== $changed) {
                        $syncObject->up($obj->getSyncName(), $obj->id, $changed);
                    }
                }

                return ['success'=>true, 'message'=>'操作成功'];
            }
            $db->rollback();
        }catch(\Exception $e){
            Log::console(implode([$e->getMessage(),"\nFILE: ", $e->getFile(), "\nLINE: ", $e->getLine(),"\nTRACE: ", $e->getTraceAsString(),"\n"]),'ERROR');
            $db->rollback();
        }

        # 还原旧值
        foreach($objectCached as list($obj, $changed, $oldValue)){
            $obj->update($oldValue);
        }

        return ['success'=>false, 'message'=>'操作失败.'];
    }

    /**
     * 移库
     */
    public static function moveStore(int $mid, int $uid, int $document_type, int $source_id, int $target_id, array $goodsInfo, string $desc) : array {
        $sourceStore = \lib\data\Store::get($source_id);
        $targetStore = \lib\data\Store::get($target_id);
        if(null == $targetStore || $targetStore->mid != $mid){
            return ['success'=>false, 'message'=>'目标仓库不存在.'];
        }

        $time = time();
        $merchant = \lib\Merchant::get($mid)->waitLock();
        $isNew = false;
        $db = new \sys\Db();
        $syncObject = new \lib\sync\Synchronize($mid, $db);
        foreach($goodsInfo as $i=>$g){
            if(null === ($sourceStoreComponents = static::waitGoodsFromStoreId($db, $source_id, $g['id'], $isNew)) || $sourceStoreComponents->mid !== $mid){
                return ['success'=>false, 'message'=>'商品不存在.'];
            }
            if($isNew){
                $syncObject->put($sourceStoreComponents->getSyncName(), $sourceStoreComponents);
            }

            if($sourceStoreComponents->store_num < $g['num']){
                return ['success'=>false, 'message'=>"库存不足."];
            }

            $targetStoreComponents = static::waitGoodsFromStoreId($db, $target_id, $g['id'], $isNew);
            if($isNew){
                $syncObject->put($targetStoreComponents->getSyncName(), $targetStoreComponents);
            }

            $goodsInfo[$i]['source'] = $sourceStoreComponents;
            $goodsInfo[$i]['target'] = $targetStoreComponents;
        }

        $no = \lib\Utils::genOrderNo('M');
        $sqls = [];
        $tp_nb_goods_documents = $db->task('tp_nb_goods_documents')->subInsert([
            'mid'           => $mid,
            'shift_id'      => $merchant->shift_id,
            'document_no'   => $no,
            'document_type' => $document_type,
            'operator_uid'  => $uid,
            'order_type'    => 256,
            'order_id'      => 0,
            'settle_mode'   => GoodsOrder::SETTLE_MODE_NOPAY,
            'amount'        => 0,
            'realpay'       => 0,
            'desc'          => $desc,
            'createtime'    => time(),
        ]);
        $doc_id = new \sys\db\ExpValue($tp_nb_goods_documents->pkVarName());
        $objectCached = [];
        foreach($goodsInfo as $i=>$g) {
            $source = $g['source']; $target = $g['target']; $num = $g['num']; $id = $g['id'];
            $goods = \lib\data\Components::get($source->goods_id);
            
            $el_components_changes[] = $db->task('el_components_changes')->subInsert([
                'mid'               => $mid,
                'shift_id'          => $merchant->shift_id,
                'operator_uid'      => $uid,
                'document_id'       => $doc_id,
                'document_type'     => $document_type,
                'goods_id'          => $id,
                'goods_name'        => $goods->name,
                'store_id'          => $sourceStore->id,
                'store_name'        => $sourceStore->name,
                'store_before'      => $source->store_num,
                'store_after'       => $source->store_num - $num,
                'num'               => $num,
                'target_id'         => $targetStore->id,
                'target_name'       => $targetStore->name,
                'target_before'     => $target->store_num,
                'target_after'      => $target->store_num + $num,
                'calc_mode'         => static::CALC_MODE_PIECE,
                'pack_size'         => $goods->pack_size,
                'pack_num'          => 0,
                'amount'            => 0,
                'createtime'        => $time,
            ]);

            $oldValue = [];
            if(null !==($changed = $source->update(['store_num'=>$source->store_num - $num], false, $oldValue))){
                $objectCached[] = [$source, $changed, $oldValue];
                $sqls[] = $db->table('tp_nb_goods_store_map')->where([['id','=',$source->id]])->dec('store_num', $num)->subUpdate();

                if($sourceStore->prop !=  $targetStore->prop && $sourceStore->prop == Store::IS_RACK && $source->on_sale){
                    # 更新总库存
                    $oldValue = [];
                    if(null !== ($changed = $goods->update(['num' => $goods->num - $num], false, $oldValue))) {
                        $tasks[] = $db->table('tp_nb_goods_rack')->where([['id','=',$goods->id]])->subUpdate($changed);
                        $objectCached[] = [$goods, $changed, $oldValue];
                    }
                }
            }

            $oldValue = [];
            if(null !==($changed = $target->update(['store_num'=>$target->store_num + $num], false, $oldValue))){
                $objectCached[] = [$target, $changed, $oldValue];
                $sqls[] = $db->table('tp_nb_goods_store_map')->where([['id','=',$target->id]])->inc('store_num', $num)->subUpdate();

                if($sourceStore->prop !=  $targetStore->prop && $targetStore->prop == Store::IS_RACK && $target->on_sale){
                    # 更新总库存
                    $oldValue = [];
                    if(null !== ($changed = $goods->update(['num' => $goods->num + $num], false, $oldValue))) {
                        $tasks[] = $db->table('tp_nb_goods_rack')->where([['id','=',$goods->id]])->subUpdate($changed);
                        $objectCached[] = [$goods, $changed, $oldValue];
                    }
                }
            }
        }
        $db->startTrans();
        try{
            if($db->batch_execute([
                $tp_nb_goods_documents,
                $el_components_changes,
                $sqls,
            ], \sys\Db::SQL_FIND)){
                $db->commit();

                # 发起同步
                $syncObject->makeSyncTask([
                    $tp_nb_goods_documents,
                    $el_components_changes,
                ]);

                # 推送同步
                foreach($objectCached as list($obj, $changed, $oldValue)){
                    $syncObject->up($obj->getSyncName(), $obj->id, $changed);
                }
                return ['success'=>true, 'message'=>'操作成功'];
            }
        }catch(\Exception $e){
            Log::console(implode([$e->getMessage(),"\nFILE: ", $e->getFile(), "\nLINE: ", $e->getLine(),"\nTRACE: ", $e->getTraceAsString(),"\n"]),'ERROR');
            $db->rollback();
        }

        # 恢复内存数据
        foreach($objectCached as list($obj, $changed, $oldValue)){
            $obj->update($oldValue);
        }

        return ['success'=>false, 'message'=>'操作失败'];
    }

    static protected function buildSyncSelect(\sys\Db $db, int $mid, int $time, int $lastId = 0) :\sys\SqlBuilder {
        if($lastId == 0){
            return $db->table(static::$TableName)->where([
                ['mid', '=', $mid],
            ])->order('id ASC');
        }
        return $db->table(static::$TableName)->where([
            ['mid', '=', $mid],
            ['id', '>', $lastId],
        ])->order('id ASC');
    }
}